#include "terrain.h"
#include <cassert>

void GenerateGridPos(int rows,int cols,float width,float depth,D3DXVECTOR3 center,
	vector<D3DXVECTOR3>& vecPos)
{
	int faceRows = rows - 1;
	int faceCols = cols - 1;
	vecPos.clear();
	vecPos.resize(rows*cols+faceRows*faceCols);
	float xPos = center.x - width/2;
	float zPos = center.z + depth/2;
	float dw = width / faceCols;
	float dd = depth / faceRows;
	for(int i = 0;i!=rows;++i)
	{
		xPos = center.x - width/2;
		for(int j = 0;j!=cols;++j)
		{
			vecPos[i*cols + j] = D3DXVECTOR3(xPos,0.0f,zPos);
			xPos += dw;
		}
		zPos -= dd;
	}
	int iCenter = rows * cols;
	zPos = center.z + depth/2 - dd/2;
	for(int i = 0;i!=faceRows;++i)
	{
		xPos = center.x - width/2 + dw/2;
		for(int j = 0;j!=faceCols;++j)
		{
			vecPos[iCenter++] = D3DXVECTOR3(xPos,0.0f,zPos);
			xPos += dw;
		}
		zPos -= dd;
	}
}

cTerrainBase::cTerrainBase():
	m_iLodLevel(LODL1),m_iRows(DefaultRows),m_iCols(DefaultCols),m_fWidth(DefaultWidth),m_fDepth(DefaultDepth),
	m_vec3Center(DefaultCenter),m_iFaceCols(m_iCols-1),m_iFaceRows(m_iRows-1),
	m_dd(DefaultDD),m_dw(DefaultDW){

}

cTerrainBase::cTerrainBase(int rows,int cols,float width,float depth,D3DXVECTOR3 center):
	m_iLodLevel(LODL1),m_iRows(rows),m_iCols(cols),m_fWidth(width),m_fDepth(depth),
	m_vec3Center(center),m_iFaceCols(cols-1),m_iFaceRows(rows-1),
	m_dd(m_fDepth/m_iFaceRows),m_dw(m_fWidth/m_iFaceCols){

}

void cTerrainBase::CreateGrid(int rows,int cols,float width,float depth, D3DXVECTOR3 center,vector<D3DXVECTOR3>& vecPos)
{
	InitData(rows,cols,width,depth,center);
	HR(D3DXCreateMeshFVF(m_iFaceRows*m_iFaceCols*4,m_iRows*m_iCols+m_iFaceCols*m_iFaceRows,D3DXMESH_VB_MANAGED|D3DXMESH_IB_DYNAMIC|D3DXMESH_32BIT,TerrainFVF,
		m_pDevice9,&m_pMesh));
	int iCenter = m_iRows*m_iCols;
	TerrainVertex* pVertex;
	HR(m_pMesh->LockVertexBuffer(0,(void**)&pVertex));
	int index;
	for(int i = 0;i!=m_iRows;++i)
	{
		for(int j = 0;j!=m_iCols;++j)
		{
			index = i*m_iCols + j;
			pVertex[index].pos = vecPos[index];
			pVertex[index].normal = D3DXVECTOR3(0.0f,1.0f,0.0f);
			pVertex[index].tex = D3DXVECTOR2(i,j);
		}
	}
	for(int i = 0;i!=m_iFaceRows;++i)
	{
		for(int j = 0;j!=m_iFaceCols;++j)
		{
			//vertex pos
			pVertex[iCenter].pos = vecPos[iCenter];
			pVertex[iCenter].normal = D3DXVECTOR3(0.0f,1.0f,0.0f);
			pVertex[iCenter].tex = (pVertex[i*m_iCols + j].tex + 
								pVertex[(i+1)*m_iCols + j + 1].tex)/2;
			++iCenter;
		}
	}
	SetBoundingSphere(pVertex);
	HR(m_pMesh->UnlockVertexBuffer());
	SetIndexAndAttribute();
	Optimize();

}
void cTerrainBase::CreateGridFromVertex(int rows,int cols,float width,float depth,D3DXVECTOR3 center,TerrainVertex* pVertex)
{
	InitData(rows,cols,width,depth,center);
	HR(D3DXCreateMeshFVF(m_iFaceRows*m_iFaceCols*4,m_iRows*m_iCols+m_iFaceCols*m_iFaceRows,D3DXMESH_VB_MANAGED|D3DXMESH_IB_DYNAMIC|D3DXMESH_32BIT,TerrainFVF,
		m_pDevice9,&m_pMesh));
	TerrainVertex* pTerrainVertex;
	HR(m_pMesh->LockVertexBuffer(0,(void**)&pTerrainVertex));
	int iIndex = 0;
	int iRowIndex = m_rID*(m_iRows - 1);
	int iColIndex = m_cID*(m_iCols - 1);
	for(int i = 0;i!=m_iRows;++i)
	{
		iColIndex = m_cID*(m_iCols - 1);
		for(int j = 0;j!=m_iCols;++j)
		{
			iIndex = i*m_iCols + j;
			pTerrainVertex[iIndex] =  pVertex[iRowIndex*m_parentCol + iColIndex];
			++iColIndex;
		}
		++iRowIndex;
	}
	++iIndex;

	int iCenter = m_parentRow*m_parentCol;
	int parentFaceCol = m_parentCol - 1;
	iRowIndex = m_rID*m_iFaceRows;
	iColIndex = m_cID*m_iFaceCols;
	for(int i = 0;i!=m_iFaceRows;++i)
	{
		iColIndex = m_cID*m_iFaceCols;
		for(int j = 0;j!=m_iFaceCols;++j)
		{
			pTerrainVertex[iIndex++] = pVertex[iCenter + iRowIndex*parentFaceCol + iColIndex];
			++iColIndex;
		}
		++iRowIndex;
	}
	SetBoundingSphere(pTerrainVertex);
	HR(m_pMesh->UnlockVertexBuffer());
	SetIndexAndAttribute();
	Optimize();
}
void cTerrainBase::GenerateIndex()
{
	int iCentralVert = m_iCols*m_iRows;
	vecLodL1Index.clear();
	vecLodL1Index.resize(m_iFaceRows*m_iFaceCols*4*3);
	int iIndex = 0;
	for(int i = 0;i!=m_iFaceRows;++i)
	{
		for(int j = 0;j!=m_iFaceCols;++j)
		{
			//top triangle
			vecLodL1Index[iIndex++] = (i*m_iCols + j);
			vecLodL1Index[iIndex++] = (i*m_iCols + j + 1);
			vecLodL1Index[iIndex++] = (iCentralVert);

			//right triangle
			vecLodL1Index[iIndex++] = (i*m_iCols + j + 1);
			vecLodL1Index[iIndex++] = ((i+1)*m_iCols + j + 1);
			vecLodL1Index[iIndex++] = (iCentralVert);

			//bottom triangle
			vecLodL1Index[iIndex++] = ((i+1)*m_iCols + j + 1);
			vecLodL1Index[iIndex++] = ((i+1)*m_iCols + j);
			vecLodL1Index[iIndex++] = (iCentralVert);

			//left triangle
			vecLodL1Index[iIndex++] = ((i+1)*m_iCols + j);
			vecLodL1Index[iIndex++] = (i*m_iCols + j);
			vecLodL1Index[iIndex++] = (iCentralVert);

			++iCentralVert;
		}
	}

	vecLodL2Index.clear();
	vecLodL2Index.resize(m_iFaceRows*m_iFaceCols*2*3);
	iIndex = 0;
	for(int i = 0;i!=m_iFaceRows;++i)
	{
		for(int j = 0;j!=m_iFaceCols;++j)
		{
			vecLodL2Index[iIndex++] = (i*m_iCols + j);
			vecLodL2Index[iIndex++] = (i*m_iCols + j + 1);
			vecLodL2Index[iIndex++] = ((i+1)*m_iCols + j);

			vecLodL2Index[iIndex++] = ((i+1)*m_iCols + j);
			vecLodL2Index[iIndex++] = (i*m_iCols + j + 1);
			vecLodL2Index[iIndex++] = ((i+1)*m_iCols + j + 1);
		}
	}
}
void cTerrainBase::SetIndex()
{
	DWORD* pIndex;
	HR(m_pMesh->LockIndexBuffer(D3DLOCK_DISCARD,(void**)&pIndex));
	switch(m_iLodLevel)
	{
	case LODL1:
		for(int i = 0;i!=vecLodL1Index.size();++i){
			pIndex[i] = vecLodL1Index[i];
		}
		break;
	case LODL2:
		for(int i = 0;i!=vecLodL2Index.size();++i){
			pIndex[i] = vecLodL2Index[i];
		}
		break;
	}
	HR(m_pMesh->UnlockIndexBuffer());
}
void cTerrainBase::SetDrawState(bool bDraw)
{
	m_bDraw = bDraw;
}
void cTerrainBase::PushTexture(string strID)
{
	if(vecTexs.size()>4){
		ERRBOX("No more than 4 textures!\n");
	}
	vecTexs.push_back(strID);
}

void cTerrainBase::Update(float dt)
{

}
void cTerrainBase::Render()
{
	if(!m_bDraw)
		return;
	cLight* pLight = GetLight();
	m_pDevice9->BeginScene();
	HR(m_pEffect->SetTechnique("TerrainTech"));
	HR(m_pEffect->SetMatrix("gWVP",&GetViewProj() ) );
	HR(m_pEffect->SetMatrix("gView",&GetView() ) );
	HR(m_pEffect->SetValue("gLightDir",&(pLight->GetDirection()),sizeof(D3DVECTOR)));

	for(int i = 0;i!=vecTexs.size();++i){
		m_pEffect->SetTexture(gTex[i].c_str(),m_pTextureMgr->GetTex(vecTexs[i]));
	}
	UINT Passes = 0;
	m_pEffect->Begin(&Passes,0);
	for(int i = 0;i!=Passes;++i)
	{
		m_pEffect->BeginPass(i);
		m_pEffect->CommitChanges();
		m_pMesh->DrawSubset(0);	
		m_pEffect->EndPass();
	}
	m_pEffect->End();
	m_pDevice9->EndScene();
}
void cTerrainBase::OnInit(string effectname)
{
	m_pShaderMgr = GetShaderMgr();
	m_pEffect = m_pShaderMgr->GetShader(effectname);
	m_pTextureMgr = GetTextureMgr();

}
void cTerrainBase::Release()
{
	SAFE_RELEASE(m_pMesh);
}
void cTerrainBase::InitData(int rows,int cols,float width,float depth,D3DXVECTOR3 center)
{
	m_iRows = rows;		m_iCols = cols;
	m_iFaceRows = m_iRows - 1;
	m_iFaceCols = m_iCols - 1;
	m_fWidth = width;	m_fDepth = depth;
	m_dw = m_fWidth / m_iFaceCols;
	m_dd = m_fDepth / m_iFaceRows;
	m_vec3Center = center;
	m_pDevice9 = GetDevice9();

	GenerateIndex();
}
void cTerrainBase::SetBoundingSphere(TerrainVertex* pVertex)
{
	HR(D3DXComputeBoundingSphere(&pVertex[0].pos,m_iRows*m_iCols+m_iFaceRows*m_iFaceCols,sizeof(TerrainVertex),&m_sphere.center,&m_sphere.radius));
}
void cTerrainBase::SetIndexAndAttribute()
{
	DWORD* pIndex;
	HR(m_pMesh->LockIndexBuffer(0,(void**)&pIndex));
	for(int i = 0;i!=vecLodL1Index.size();++i)
	{
		pIndex[i] = vecLodL1Index[i];
	}
	HR(m_pMesh->UnlockIndexBuffer());
	DWORD* pAttribute;
	HR(m_pMesh->LockAttributeBuffer(0,&pAttribute));
	for(int i = 0;i!=m_iFaceCols*m_iFaceRows*2;++i)
	{
		pAttribute[i] = 0;
	}
	HR(m_pMesh->UnlockAttributeBuffer());

}
void cTerrainBase::Optimize()
{
	DWORD* adjacencyInfo = new DWORD[m_pMesh->GetNumFaces()*3];
	m_pMesh->GenerateAdjacency(EPSILON,adjacencyInfo);
	m_pMesh->OptimizeInplace(D3DXMESH_MANAGED|D3DXMESHOPT_ATTRSORT,adjacencyInfo,NULL,NULL,NULL);
	delete [] adjacencyInfo;	
}